#include "src/pbc/transform/portable-execute.h"

#include <fstream>

#include "src/api/api-inl.h"
#include "src/api/api-macros.h"
#include "src/execution/vm-state-inl.h"
#include "src/pbc/transform/portable-deserializer.h"
#include "src/pbc/transform/portable-transformer.h"

namespace portable {
void ReadBinaryFile(const char* file_name, std::vector<uint8_t>& buffer) {
  std::ifstream infile(file_name, std::ifstream::binary);
  CHECK(infile.good());
  infile.seekg(0, std::ifstream::end);
  size_t file_size = infile.tellg();
  infile.seekg(0);
  buffer.resize(file_size);
  infile.read(reinterpret_cast<char*>(buffer.data()), file_size);
}

#if V8_MAJOR_VERSION == 11
v8::MaybeLocal<v8::UnboundScript> PortableTransformUnboundInternal(
    v8::Isolate* isolate, const uint8_t* data, int length, bool log_pbc,
    v8::ScriptCompiler::CompileOptions compile_options,
    v8::ScriptCompiler::CachedData* cached_data, std::string filename) {
  using namespace v8;
#ifdef V8_OS_OHOS
  v8::internal::StdoutStream{}
      << "[pbc] Enter PortableTransformUnboundInternal \n";
#endif
  TRACE_EVENT0("v8," TRACE_DISABLED_BY_DEFAULT("v8.compile"),
               "V8.AOT.PortableTransformUnboundInternal");
  i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
  ENTER_V8_NO_SCRIPT(i_isolate, isolate->GetCurrentContext(), ScriptCompiler,
                     PortableTransformUnboundInternal,
                     MaybeLocal<UnboundScript>(), InternalEscapableScope);

  i::Handle<i::SharedFunctionInfo> top_sfi;

  // 1. deserialize
  if (log_pbc) {
    std::cout << "==================== 1. deserialize ===================="
              << std::endl;
  }
  i::PbcDeserializer deserializer;
  const i::PbcScript* pbc_script =
      deserializer.Run(data, static_cast<uint32_t>(length));
  if (log_pbc) {
    pbc_script->Dump();
  }
  // use hash let dummy source and filename be unique
  std::string hash_str = std::to_string(pbc_script->GetHashValue());
  std::string dummy_source =
      std::string(v8::internal::PbcTransformerBase::dummy_prefix_) +
      "/*pbc_hash:" + hash_str + "*/";
  filename = hash_str + "-" + filename;

  // if has code cache and try to consume it
  bool use_cache =
      cached_data && compile_options == v8::ScriptCompiler::kConsumeCodeCache;
  bool lazy_transform_pbc =
      compile_options != v8::ScriptCompiler::kEagerCompile;
#ifdef V8_OS_OHOS
  v8::internal::StdoutStream{} << "[pbc] use_cache: " << use_cache << "\n";
#endif
  if (use_cache) {
    if (log_pbc) {
      std::cout << "try to consume code cache, its length: "
                << cached_data->length << "\n";
    }
    // AlignedCachedData takes care of pointer-aligning the data.
    auto aligned_cached_data = std::make_unique<i::AlignedCachedData>(
        cached_data->data, cached_data->length);
    i::Handle<i::String> resource_name =
        v8::internal::UtilsInternalizeString(*i_isolate, filename);
    v8::internal::ScriptDetails script_details(resource_name, compile_options);
    i::Handle<i::String> pbc_dummy_source =
        v8::internal::UtilsInternalizeString(*i_isolate, dummy_source);
    auto maybe_function_info =
        i::Compiler::GetSharedFunctionInfoForScriptWithCachedData(
            i_isolate, pbc_dummy_source, script_details,
            aligned_cached_data.get(), compile_options,
            v8::ScriptCompiler::NoCacheReason::kNoCacheNoReason,
            i::NOT_NATIVES_CODE);
    cached_data->rejected = aligned_cached_data->rejected();
    if (log_pbc) {
      std::cout << "cached_data rejected: " << aligned_cached_data->rejected()
                << std::endl;
    }
#ifdef V8_OS_OHOS
    v8::internal::StdoutStream{}
        << "cached_data rejected: " << aligned_cached_data->rejected() << "\n";
#else
    if (aligned_cached_data->rejected()) {
      FATAL("failed to consume cache");
      P_UNREACHABLE();
    }
    if (!maybe_function_info.ToHandle(&top_sfi)) {
      FATAL("failed to get function info");
      P_UNREACHABLE();
    }
#endif
  }

  if (!use_cache || cached_data->rejected) {
    // 2. transform
    if (log_pbc) {
      std::cout << "==================== 2. transform ===================="
                << std::endl;
    }
    i::PbcTransformer transformer(*pbc_script, *i_isolate, lazy_transform_pbc,
                                  filename, dummy_source);
    top_sfi = transformer.Run();
#ifdef V8_OS_OHOS
    v8::internal::StdoutStream{} << "[pbc] top PbcTransformer done \n";
#endif
  }

  i::Handle<i::Script> i_script(i::Script::cast(top_sfi->script()), i_isolate);
  if (lazy_transform_pbc && !i_script->HasValidPbcSource()) {
    v8::Local<v8::String> pbc_source_string =
        v8::String::NewFromOneByte(isolate, data, v8::NewStringType::kNormal,
                                   length)
            .ToLocalChecked();
    i::Handle<i::String> pbc_source = v8::Utils::OpenHandle(*pbc_source_string);
    i_script->set_pbc_source(*pbc_source);
  }
#ifdef OBJECT_PRINT
  if (log_pbc) {
    top_sfi->HeapObjectPrint(std::cout);
    CHECK(top_sfi->HasBytecodeArray());
    i::Handle<i::BytecodeArray> bytecode(top_sfi->GetBytecodeArray(i_isolate),
                                         i_isolate);
    bytecode->HeapObjectPrint(std::cout);
  }
#endif  // OBJECT_PRINT

  RETURN_ON_FAILED_EXECUTION(v8::UnboundScript);
  RETURN_ESCAPED(v8::ToApiHandle<v8::UnboundScript>(top_sfi));
}

#else
v8::MaybeLocal<v8::UnboundScript> PortableTransformUnboundInternal(
    v8::Isolate* isolate, const uint8_t* data, int length, bool log_pbc,
    v8::ScriptCompiler::CompileOptions compile_options,
    v8::ScriptCompiler::CachedData* cached_data, std::string filename) {
  using namespace v8;
  i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
  ENTER_V8_NO_SCRIPT(i_isolate, isolate->GetCurrentContext(), ScriptCompiler,
                     PortableTransformUnboundInternal, InternalEscapableScope);

  i::Handle<i::SharedFunctionInfo> top_sfi;

  // 1. deserialize
  if (log_pbc) {
    std::cout << "==================== 1. deserialize ===================="
              << std::endl;
  }
  i::PbcDeserializer deserializer;
  const i::PbcScript* pbc_script =
      deserializer.Run(data, static_cast<uint32_t>(length));
  if (log_pbc) {
    pbc_script->Dump();
  }
  // use hash let dummy source and filename be unique
  std::string hash_str = std::to_string(pbc_script->GetHashValue());
  std::string dummy_source =
      std::string(v8::internal::PbcTransformerBase::dummy_prefix_) +
      "/*pbc_hash:" + hash_str + "*/";
  filename = hash_str + "-" + filename;

  // if has code cache and try to consume it
  bool use_cache =
      cached_data && compile_options == v8::ScriptCompiler::kConsumeCodeCache;
  bool lazy_transform_pbc = compile_options != v8::ScriptCompiler::kEagerCompile;
#ifdef V8_OS_OHOS
  v8::internal::StdoutStream{} << "[pbc] use_cache: " << use_cache << "\n";
#endif
  if (use_cache) {
    if (log_pbc) {
      std::cout << "try to consume code cache, its length: "
                << cached_data->length << "\n";
    }
    // AlignedCachedData takes care of pointer-aligning the data.
    auto aligned_cached_data = std::make_unique<i::AlignedCachedData>(
        cached_data->data, cached_data->length);
    i::Handle<i::String> resource_name =
        v8::internal::UtilsInternalizeString(*i_isolate, filename);
    v8::internal::ScriptDetails script_details(resource_name, compile_options);
    i::Handle<i::String> pbc_dummy_source =
        v8::internal::UtilsInternalizeString(*i_isolate, dummy_source);
    v8::ScriptCompiler::CompilationDetails compilation_details;
    auto maybe_function_info =
        i::Compiler::GetSharedFunctionInfoForScriptWithCachedData(
            i_isolate, pbc_dummy_source, script_details,
            aligned_cached_data.get(), compile_options,
            v8::ScriptCompiler::NoCacheReason::kNoCacheNoReason,
            i::NOT_NATIVES_CODE, &compilation_details);
    cached_data->rejected = aligned_cached_data->rejected();
    if (log_pbc) {
      std::cout << "cached_data rejected: " << aligned_cached_data->rejected()
                << std::endl;
    }
#ifdef V8_OS_OHOS
    v8::internal::StdoutStream{}
        << "cached_data rejected: " << aligned_cached_data->rejected() << "\n";
#else
    if (aligned_cached_data->rejected()) {
      FATAL("failed to consume cache");
      P_UNREACHABLE();
    }
    i::DirectHandle<i::SharedFunctionInfo> sfi;
    if (!maybe_function_info.ToHandle(&sfi)) {
      FATAL("failed to get function info");
      P_UNREACHABLE();
    }
    top_sfi = sfi.operator->();
#endif
  }

  if (!use_cache || cached_data->rejected) {
    // 2. transform
    if (log_pbc) {
      std::cout << "==================== 2. transform ===================="
                << std::endl;
    }
    i::PbcTransformer transformer(*pbc_script, *i_isolate, lazy_transform_pbc,
                                  filename, dummy_source);
    top_sfi = transformer.Run();
  }

  i::Handle<i::Script> i_script(Cast<i::Script>(top_sfi->script()), i_isolate);
  if (lazy_transform_pbc && !i_script->HasValidPbcSource()) {
    v8::Local<v8::String> pbc_source_string =
        v8::String::NewFromOneByte(isolate, data, v8::NewStringType::kNormal,
                                   length)
            .ToLocalChecked();
    i::Handle<i::String> pbc_source = v8::Utils::OpenHandle(*pbc_source_string);
    i_script->set_pbc_source(*pbc_source);
  }
#ifdef OBJECT_PRINT
  if (log_pbc) {
    top_sfi->HeapObjectPrint(std::cout);
    CHECK(top_sfi->HasBytecodeArray());
    top_sfi->GetBytecodeArray(i_isolate)->HeapObjectPrint(std::cout);
  }
#endif  // OBJECT_PRINT

  RETURN_ON_FAILED_EXECUTION(v8::UnboundScript);
  RETURN_ESCAPED(v8::ToApiHandle<v8::UnboundScript>(top_sfi));
}

#endif

/*
bool ExecutePbc(v8::Isolate* isolate, v8::Local<v8::Context> realm,
                const std::vector<uint8_t>& buffer, bool log_pbc,
                bool lazy_transform_pbc) {
  // 1. deserialize
  if (log_pbc) {
    std::cout << "==================== 1. deserialize ===================="
              << std::endl;
  }
  i::PbcDeserializer deserializer;
  const i::PbcScript* pbc_script =
      deserializer.Run(buffer.data(), static_cast<uint32_t>(buffer.size()));
  if (log_pbc) {
    pbc_script->Dump();
  }

  // 2. transform
  if (log_pbc) {
    std::cout << "==================== 2. transform ===================="
              << std::endl;
  }
  i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
  v8::HandleScope handle_scope(isolate);
  v8::TryCatch try_catch(isolate);
  try_catch.SetVerbose(true);

  i::PbcTransformer transformer(*pbc_script, *i_isolate, log_pbc,
                                lazy_transform_pbc);
  i::Handle<i::SharedFunctionInfo> top_sfi = transformer.Run();
  if (lazy_transform_pbc) {
    v8::Local<v8::String> pbc_source_string =
        v8::String::NewFromOneByte(isolate, buffer.data(),
                                   v8::NewStringType::kNormal,
                                   static_cast<int>(buffer.size()))
            .ToLocalChecked();
    i::Handle<i::String> pbc_source = v8::Utils::OpenHandle(*pbc_source_string);

#if V8_MAJOR_VERSION == 11
    i::Handle<i::Script> i_script(i::Script::cast(top_sfi->script()),
                                  i_isolate);
#else
    i::Handle<i::Script> i_script(Cast<i::Script>(top_sfi->script()),
                                  i_isolate);
#endif
    i_script->set_pbc_source(*pbc_source);
  }
#ifdef OBJECT_PRINT
  if (log_pbc) {
    top_sfi->HeapObjectPrint(std::cout);
    CHECK(top_sfi->HasBytecodeArray());
#if V8_MAJOR_VERSION == 11
    i::Handle<i::BytecodeArray> bytecode(top_sfi->GetBytecodeArray(i_isolate),
                                         i_isolate);
    bytecode->HeapObjectPrint(std::cout);
#else
    top_sfi->GetBytecodeArray(i_isolate)->HeapObjectPrint(std::cout);
#endif
  }
#endif  // OBJECT_PRINT
  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  v8::Context::Scope scope(context);
#if V8_MAJOR_VERSION == 11
  v8::Local<v8::UnboundScript> result =
      v8::ToApiHandle<v8::UnboundScript>(top_sfi);
#else
  v8::MaybeLocal<v8::UnboundScript> maybe_script =
      v8::ToApiHandle<v8::UnboundScript>(top_sfi);
  v8::Local<v8::UnboundScript> result;
  if (!maybe_script.ToLocal(&result)) {
    CHECK(false);
  }
#endif
  v8::Local<v8::Script> script = result->BindToCurrentContext();

  v8::Context::Scope context_scope(realm);

  if (log_pbc) {
    std::cout << std::endl;
    v8::Local<v8::String> res_name =
        script->GetResourceName()->ToString(context).ToLocalChecked();
    v8::String::Utf8Value utf8_value(isolate, res_name);
    char* str = (*utf8_value);
    std::cout << "==================== 3. Script::Run (" << str
              << ") ====================" << std::endl;
  }
  auto maybe_result = script->Run(realm);
  v8::Local<v8::Value> result2;
  if (!maybe_result.ToLocal(&result2)) {
    DCHECK(try_catch.HasCaught());
    return false;
  }

  if (try_catch.HasCaught()) return false;
  return true;
}
*/

}  // namespace portable